/*
 * $Id: log.c,v 1.37 2006-02-07 08:04:12 bacon Exp $
 */

#include <xp/utl/log.h>
#include <xp/bas/memory.h>
#include <xp/bas/stdarg.h>
#include <xp/bas/string.h>
#include <xp/bas/time.h>
#include <xp/bas/sio.h>
#include <xp/bas/locale.h>
#include <xp/bas/stdio.h>

#define MASK_PRIORITY 0x00FF
#define MASK_TARGET   0xFF00

#ifdef _WIN32
	/* TODO: windows event log */
#else
	#include <syslog.h>
#endif

static int __report (
	const xp_char_t* tm, xp_sio_t* sio,
	const xp_char_t* ident, const xp_char_t* msg);

static const xp_char_t* __priority_names[] =
{
	XP_TEXT("panic"),
	XP_TEXT("alert"),
	XP_TEXT("critical"),
	XP_TEXT("error"),
	XP_TEXT("warning"),
	XP_TEXT("notice"),
	XP_TEXT("info"),
	XP_TEXT("debug"),
	XP_NULL
};


#ifdef _WIN32
/* TODO: windows event logging */
#else

static int __syslog_priority[] =
{
	LOG_EMERG,
	LOG_ALERT,
	LOG_CRIT,
	LOG_ERR,
	LOG_WARNING,
	LOG_NOTICE,
	LOG_INFO,
	LOG_DEBUG
};

#endif

xp_log_t* xp_log_open (xp_log_t* log, const xp_char_t* ident, int type, ...)
{
	xp_va_list ap;
	xp_bool_t has_target = xp_false;

	if (log == XP_NULL) {
		log = (xp_log_t*) xp_malloc (xp_sizeof(xp_log_t));
		if (log == XP_NULL) return XP_NULL;
		log->__dynamic = xp_true;
	}
	else log->__dynamic = xp_false;

	log->type = type;
	if (type & XP_LOG_FILE) {
		xp_va_start (ap, type);
		xp_strxcpy (log->target.file, 
			xp_countof(log->target.file), xp_va_arg(ap, const xp_char_t*));
		xp_va_end (ap);
		has_target = xp_true;
	}

	if ((type & XP_LOG_CONSOLE) || (type & XP_LOG_SYSLOG)) {
		has_target = xp_true;
	}

	if (!has_target) {
		if (log->__dynamic) xp_free (log);
		return XP_NULL;
	}

	if (ident == XP_NULL) log->ident[0] = XP_CHAR('\0');
	else xp_strxcpy (log->ident, xp_countof(log->ident), ident);

	if (xp_mtx_open(&log->mtx) == XP_NULL) {
		if (log->__dynamic) xp_free (log);
		return XP_NULL;
	}

#ifdef _WIN32
	/* TODO: windows event logging */
#else
	log->syslog_facility = LOG_USER;
#endif
	return log;
}

void xp_log_close (xp_log_t* log)
{
	if (log->type & XP_LOG_CONSOLE) {
		/* TODO: need mtx here? */
		xp_sio_flush (xp_sio_err);
	}

	xp_mtx_close (&log->mtx);
	if (log->__dynamic) xp_free (log);
}

int xp_log_clear (xp_log_t* log)
{
	if (log->type & XP_LOG_FILE) {
		xp_sio_t sio;
		if (xp_sio_open(&sio, 
			log->target.file, XP_TEXT("w")) == XP_NULL) return -1;
		xp_sio_close (&sio);
	}

	return 0;
}

void xp_log_setident (xp_log_t* log, const xp_char_t* ident)
{
	if (ident == XP_NULL) log->ident[0] = XP_CHAR('\0');
	else xp_strxcpy (log->ident, xp_countof(log->ident), ident);
}

void xp_log_settarget (xp_log_t* log, int type)
{
	log->type = (log->type & MASK_PRIORITY) | (type & MASK_TARGET);
}

void xp_log_settargetfile (xp_log_t* log, const xp_char_t* file)
{
	xp_strxcpy (log->target.file, xp_countof(log->target.file), file);
}

void xp_log_setpriority (xp_log_t* log, int priority)
{
	log->type = (log->type & MASK_TARGET) | (priority & MASK_PRIORITY);
}

int xp_log_setprioritybyname (xp_log_t* log, const xp_char_t* name)
{
	const xp_char_t** p = __priority_names;

	while (*p != XP_NULL) {
		if (xp_strcmp (*p, name) == 0) {
			xp_log_setpriority (log, (int)(p - __priority_names));
			return 0;
		}

		p++;
	}

	return -1;
}

void xp_log_setsyslogfacility (xp_log_t* log, int facility)
{
#ifndef _WIN32
	log->syslog_facility = facility;
#endif
}

int xp_log_getpriority (xp_log_t* log)
{
	return log->type & MASK_PRIORITY;
}	

const xp_char_t* xp_log_getpriorityname (xp_log_t* log)
{
	int pri = log->type & MASK_PRIORITY;

	if (pri < 0 || pri >= xp_countof(__priority_names)) return XP_NULL;
	return __priority_names[pri];
}

int xp_log_gettarget (xp_log_t* log)
{
	return log->type & MASK_TARGET;
}

int xp_log_report (xp_log_t* log, 
	const xp_char_t* ident, int pri, const xp_char_t* fmt, ...)
{
	xp_time_t now;
	xp_tm_t cnow;
	xp_char_t tm[64];
	xp_va_list ap;
	xp_char_t msg_buf[XP_LOG_MSG_MAX + 1];
	xp_char_t idt_buf[XP_LOG_IDENT_MAX * 2 + 1];
	const xp_char_t* idt = XP_NULL;
	int n1 = 0, n2 = 0;
	/*int gmtoff_hour, gmtoff_min;*/

	if (log == XP_NULL) return 0;
	if ((log->type & MASK_TARGET) == 0) return 0;
	if (pri > (log->type & MASK_PRIORITY)) return 0;

	if (xp_gettime(&now) == -1) return -1;
	if (xp_localtime(now, &cnow) == -1) return -1;

	if (xp_formattime (tm, xp_countof(tm),
		XP_TEXT("%Y-%m-%d %H:%M:%S %z"), &cnow) == 0) return -1;

	/*
	gmtoff_hour = cnow.gmtoff / XP_SEC_IN_HOUR;
	gmtoff_min = (cnow.gmtoff % XP_SEC_IN_HOUR) / XP_SEC_IN_MIN;
	xp_sprintf (tm, xp_countof(tm),
		XP_TEXT("%04.4d-%02d-%02d %02d:%02d:%02d %c%02d%02d"), 
		cnow.year + XP_CTIME_BASE_YEAR, cnow.mon + 1, cnow.day, 
		cnow.hour, cnow.min, cnow.sec, 
		((cnow.gmtoff > 0)? XP_CHAR('+'): XP_CHAR('-')),
		gmtoff_hour, gmtoff_min);
	*/

	if (ident == XP_NULL || ident[0] == XP_CHAR('\0')) {
		if (log->ident[0] != XP_CHAR('\0')) idt = log->ident;
	}
	else {
		if (log->ident[0] != XP_CHAR('\0')) {
			xp_strxapp (
				idt_buf, xp_countof(idt_buf), log->ident, 
				XP_TEXT("("), ident, XP_TEXT(")"), XP_NULL);
			idt = idt_buf;
		}
		else idt = ident;
	}

	xp_va_start (ap, fmt);
	/* TODO: some way not to truncate the message */
	if (xp_vsprintf(msg_buf, xp_countof(msg_buf), fmt, ap) == -1) {
		xp_va_end (ap);
		return -1;
	}
	xp_va_end (ap);

	if (xp_mtx_lock (&log->mtx) == -1) return -1;

	if (log->type & XP_LOG_CONSOLE) {
		n1 = __report (tm, xp_sio_err, idt, msg_buf);
	}

	if (log->type & XP_LOG_FILE) {
		xp_sio_t sio;
		if (xp_sio_open(&sio, 
			log->target.file, XP_TEXT("a")) != XP_NULL) {
			n2 = __report (tm, &sio, idt, msg_buf);
			xp_sio_close (&sio);
		}
		else n2 = -1;
	}

	if (log->type & XP_LOG_SYSLOG) {
#ifdef _WIN32
		/* TODO: windows event log */
#else
		int sl_pri = (pri < xp_countof(__syslog_priority))? 
			__syslog_priority[pri]: LOG_DEBUG;

	#ifdef XP_CHAR_IS_MCHAR
		if (idt != XP_NULL) 
			openlog (idt, 0, log->syslog_facility);
		syslog (sl_pri, "%s", msg_buf);
	#else
		xp_mchar_t msg_mb[XP_LOG_MSG_MAX];

		if (idt != XP_NULL) {
			xp_mchar_t idt_mb[XP_LOG_IDENT_MAX];
			xp_wcstomcs (idt, idt_mb, xp_countof(idt_mb));
			openlog (idt_mb, 0, log->syslog_facility);
		}

		/* 
		 * don't care about the maximum length when converted 
		 * to a multi-byte string. a message can be truncated 
		 * furthur due to this
		 */
		xp_wcstomcs (msg_buf, msg_mb, xp_countof(msg_mb));
		syslog (sl_pri, "%s", msg_mb);
	#endif
		if (idt != XP_NULL) closelog ();
#endif
	}

	xp_mtx_unlock (&log->mtx);
	return (n1 == 0 && n2 == 0)? 0: -1;
}

static int __report (
	const xp_char_t* tm, xp_sio_t* sio,
	const xp_char_t* ident, const xp_char_t* msg)
{
	if (ident == XP_NULL) {
		xp_sio_putsn (sio, tm, 
			XP_TEXT(" "), msg, XP_TEXT("\n"), XP_NULL);
	}
	else {
		xp_sio_putsn (sio, tm, XP_TEXT(" "), 
			ident, XP_TEXT(": "), msg, XP_TEXT("\n"), XP_NULL);
	}

	return 0;
}

const xp_char_t* xp_getlogpriorityname (int pri)
{
	if (pri < 0 || pri >= xp_countof(__priority_names)) return XP_NULL;
	return __priority_names[pri];
}
